package perfume

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"mall/common/cachex"
	"mall/common/ctxdatax"
	"mall/common/globalkey"
	"mall/common/modelx"
	"mall/common/moneyx"
	"mall/service/backend/common/utils/pointy"
	"mall/service/backend/rpc/backendclient"
	"mall/service/blog/rpc/blogclient"
	"mall/service/forum/api/internal/logic/util"
	"mall/service/forum/api/internal/svc"
	"mall/service/forum/api/internal/types"
	"mall/service/forum/api/internal/utils/parse"

	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type GetPerfumeListLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewGetPerfumeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPerfumeListLogic {
	return &GetPerfumeListLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *GetPerfumeListLogic) GetPerfumeList(req *types.PerfumeListReq) (*types.PerfumeListResp, error) {
	userId := ctxdatax.GetUserId(l.ctx)

	page, pageSize := parse.ParsePageInfo(
		req.Current,
		req.PageSize,
		l.svcCtx.Config.Query.DefaultAmount,
		l.svcCtx.Config.Query.MaxAmount,
	)

	var title string
	if req.Title != nil {
		title = strings.TrimSpace(*req.Title)
	}
	var slug string
	if req.Slug != nil {
		slug = strings.TrimSpace(*req.Slug)
	}
	var cacheKey string
	if len(title) == 0 && len(slug) == 0 { // 不是搜索
		// 缓存推荐品牌
		recommendedResp, ok := l.recommendedList(page, pageSize)
		if ok {
			return recommendedResp, nil
		}

		if page == 1 { // 缓存第一页
			cacheKey = cachex.ForumHomepage
		}
	}

	var preload string = "blogBook:Id,Title,Slug;blogMedia:Id,Uid,Url,Thumbnail;blogBookMeta:year,reviewCount,averageRating"
	var eagerLoad string = "blogBook:blogMedia,blogBookMeta"
	var with string
	if req.With != nil {
		with = strings.TrimSpace(*req.With)
	}
	if len(with) > 0 {
		preload += ";blogBookRelation:" + with
		eagerLoad += ",blogBookRelation"
	}
	var termTaxonomyKnowledgeBase string = strconv.FormatInt(int64(globalkey.TermTaxonomyKnowledgeBase), 10)
	var preFilter string = "Slug,equalTo:perfumes;Taxonomy,equalTo:" + termTaxonomyKnowledgeBase
	if req.Filter != nil && len(strings.TrimSpace(*req.Filter)) > 0 {
		filterMap := util.GetFilterMap(strings.TrimSpace(*req.Filter))
		for key, _ := range filterMap {
			switch key {
			case "brand":
				preFilter = "Slug,equalTo:brands;Taxonomy,equalTo:" + termTaxonomyKnowledgeBase
			case "perfume":
				preFilter = "Slug,equalTo:perfumes;Taxonomy,equalTo:" + termTaxonomyKnowledgeBase
			case "nose":
				preFilter = "Slug,equalTo:perfumers;Taxonomy,equalTo:" + termTaxonomyKnowledgeBase
			case "group":
				preFilter = "Slug,equalTo:fragrance families;Taxonomy,equalTo:" + termTaxonomyKnowledgeBase
			case "note":
				preFilter = "Slug,equalTo:notes;Taxonomy,equalTo:" + termTaxonomyKnowledgeBase
			}
		}
	}
	var filters []string
	filters = append(filters, "Status,equalTo:"+strconv.FormatInt(int64(globalkey.StatusEnabled), 10))

	var sorter string
	if req.Sorter != nil {
		sorter = strings.TrimSpace(*req.Sorter)
	}
	var platformId uint32 = globalkey.ThirdPartyPlatformWechatMini

	if len(cacheKey) > 0 { // 缓存第一页
		localCacheResp, ok := l.svcCtx.LocalCache.Get(cacheKey)
		if ok {
			return localCacheResp.(*types.PerfumeListResp), nil
		}

		resp, err := l.svcCtx.BlogRpc.BookList(l.ctx, &blogclient.BookListReq{
			Offset:          int32((page - 1) * pageSize),
			Limit:           int32(pageSize),
			Preload:         preload,
			EagerLoad:       eagerLoad,
			Filter:          strings.Join(filters, ";"),
			Sorter:          sorter,
			PreFilterEntity: "blogTerm",
			PreFilter:       preFilter,
			VisitorId:       int64(userId),
			PlatformId:      int32(platformId),
		})
		if err != nil {
			return nil, err
		}

		cacheResp := l.parsePerfumeList(resp, page, pageSize)

		l.svcCtx.LocalCache.Set(cacheKey, cacheResp)

		return cacheResp, nil
	}

	resp, err := l.svcCtx.BlogRpc.BookList(l.ctx, &blogclient.BookListReq{
		Offset:          int32((page - 1) * pageSize),
		Limit:           int32(pageSize),
		Preload:         preload,
		EagerLoad:       eagerLoad,
		Title:           title,
		Slug:            slug,
		Filter:          strings.Join(filters, ";"),
		Sorter:          sorter,
		PreFilterEntity: "blogTerm",
		PreFilter:       preFilter,
		VisitorId:       int64(userId),
		PlatformId:      int32(platformId),
	})
	if err != nil {
		return nil, err
	}

	return l.parsePerfumeList(resp, page, pageSize), nil
}

func (l *GetPerfumeListLogic) parsePerfumeList(
	resp *blogclient.BookListResp,
	page uint64,
	pageSize uint64,
) *types.PerfumeListResp {
	var hasNextPage bool
	var hasPreviousPage bool
	if resp.HasNextPage > 0 {
		hasNextPage = true
	}
	if resp.HasPreviousPage > 0 {
		hasPreviousPage = true
	}

	ret := &types.PerfumeListResp{
		BaseListInfo: types.BaseListInfo{
			Current:         page,
			PageSize:        pageSize,
			HasNextPage:     hasNextPage,
			HasPreviousPage: hasPreviousPage,
		},
	}

	if len(resp.Data) > 0 {
		for _, item := range resp.Data {
			var sections []*types.TermInfo
			if len(item.Sections) > 0 {
				for _, v := range item.Sections {
					sections = append(sections, &types.TermInfo{
						Id:   pointy.GetPointer(uint64(v.Id)),
						Name: pointy.GetPointer(v.Name),
						Slug: pointy.GetPointer(v.Slug),
					})
				}
			}
			var media []*types.TmpMediaInfo
			if len(item.Media) > 0 {
				for _, v := range item.Media {
					media = append(media, &types.TmpMediaInfo{
						Uid:       pointy.GetPointer(uint64(v.Uid)),
						Id:        pointy.GetPointer(strconv.FormatInt(int64(v.Id), 10)),
						Url:       pointy.GetPointer(v.Url),
						Thumbnail: pointy.GetPointer(uint32(v.Thumbnail)),
					})
				}
			}
			var brands []*types.KbEntryInfo
			var perfumes []*types.KbEntryInfo
			var noses []*types.KbEntryInfo
			var groups []*types.KbEntryInfo
			var topNotes []*types.KbEntryInfo
			var heartNotes []*types.KbEntryInfo
			var baseNotes []*types.KbEntryInfo
			if len(item.Relations) > 0 {
				for _, v := range item.Relations {
					switch v.Section {
					case "brand":
						brands = append(brands, &types.KbEntryInfo{
							Id:    pointy.GetPointer(uint64(v.Id)),
							Title: pointy.GetPointer(v.Title),
							Slug:  pointy.GetPointer(v.Slug),
						})
					case "perfume":
						perfumes = append(perfumes, &types.KbEntryInfo{
							Id:    pointy.GetPointer(uint64(v.Id)),
							Title: pointy.GetPointer(v.Title),
							Slug:  pointy.GetPointer(v.Slug),
						})
					case "nose":
						noses = append(noses, &types.KbEntryInfo{
							Id:    pointy.GetPointer(uint64(v.Id)),
							Title: pointy.GetPointer(v.Title),
							Slug:  pointy.GetPointer(v.Slug),
						})
					case "group":
						groups = append(groups, &types.KbEntryInfo{
							Id:    pointy.GetPointer(uint64(v.Id)),
							Title: pointy.GetPointer(v.Title),
							Slug:  pointy.GetPointer(v.Slug),
						})
					case "note":
						switch int32(v.GroupId) {
						case globalkey.KBEntryTopNote:
							topNotes = append(topNotes, &types.KbEntryInfo{
								Id:    pointy.GetPointer(uint64(v.Id)),
								Title: pointy.GetPointer(v.Title),
								Slug:  pointy.GetPointer(v.Slug),
							})
						case globalkey.KBEntryHeartNote:
							heartNotes = append(heartNotes, &types.KbEntryInfo{
								Id:    pointy.GetPointer(uint64(v.Id)),
								Title: pointy.GetPointer(v.Title),
								Slug:  pointy.GetPointer(v.Slug),
							})
						case globalkey.KBEntryBaseNote:
							baseNotes = append(baseNotes, &types.KbEntryInfo{
								Id:    pointy.GetPointer(uint64(v.Id)),
								Title: pointy.GetPointer(v.Title),
								Slug:  pointy.GetPointer(v.Slug),
							})
						}
					}
				}
			}
			var year *string
			var reviewCount *uint32
			var averageRating *string
			var recommended *bool
			if len(item.Meta) > 0 {
				for _, v := range item.Meta {
					switch v.Key {
					case "year":
						year = pointy.GetPointer(v.Value)
					case "reviewCount":
						if i, err := strconv.ParseInt(v.Value, 10, 32); err == nil {
							reviewCount = pointy.GetPointer(uint32(i))
						}
					case "averageRating":
						if f, err := strconv.ParseFloat(v.Value, 64); err == nil {
							averageRating = pointy.GetPointer(moneyx.FormatStar(f, 1))
						}
					case "recommended":
						recommended = pointy.GetPointer(true)
					}
				}
			}

			ret.List = append(ret.List, &types.KbEntryInfo{
				Id:            pointy.GetPointer(uint64(item.Id)),
				Title:         pointy.GetPointer(item.Title),
				Slug:          pointy.GetPointer(item.Slug),
				CommentStatus: pointy.GetPointer(uint32(item.CommentStatus)),
				CreatedAt:     pointy.GetPointer(item.Date),
				UpdatedAt:     pointy.GetPointer(item.Modified),
				Sections:      sections,
				Media:         media,
				Brands:        brands,
				Perfumes:      perfumes,
				Noses:         noses,
				Groups:        groups,
				TopNotes:      topNotes,
				HeartNotes:    heartNotes,
				BaseNotes:     baseNotes,
				Year:          year,
				AverageRating: averageRating,
				ReviewCount:   reviewCount,
				Recommended:   recommended,
			})
		}
	}

	return ret
}

func (l *GetPerfumeListLogic) recommendedList(page uint64, pageSize uint64) (resp *types.PerfumeListResp, ok bool) {
	localCacheResp, ok := l.svcCtx.LocalCache.Get(cachex.ForumBrand)
	if ok {
		return localCacheResp.(*types.PerfumeListResp), true
	}

	brandIds, err := l.svcCtx.BizRedis.GetCtx(l.ctx, cachex.ForumRecommendBrand)
	if err != nil {
		logrus.Info(fmt.Sprintf("GetPerfumeList recommendedList redis err: %+v", err))
		return
	}

	logrus.Info(fmt.Sprintf("GetPerfumeList recommendedList redis brandIds: %v", brandIds))
	// 20230802:42147,42168
	today := time.Now().Format("20060102")
	todayPrefix := today + ":"
	if brandIds == todayPrefix { // 没有推荐品牌，直接返回，为空则查询BackendRpc RecommendPostList
		return
	}

	var bids string
	notExpired := strings.HasPrefix(brandIds, todayPrefix)
	if len(brandIds) == 0 || !notExpired {
		res, err := l.svcCtx.BackendRpc.RecommendPostList(l.ctx, &backendclient.RecommendPostListReq{
			Date: today,
			Type: "brand",
		})
		if err != nil {
			logrus.Info(fmt.Sprintf("GetPerfumeList recommendedList BackendRpc RecommendPostList err: %+v", err))
			return
		}

		if len(res.List) > 0 {
			var eids []string
			for _, item := range res.List {
				eids = append(eids, strconv.FormatInt(item.EntryId, 10))
			}
			bids = strings.Join(eids, ",")
		}

		err = l.svcCtx.BizRedis.SetCtx(l.ctx, cachex.ForumRecommendBrand, todayPrefix+bids)
		if err != nil {
			logrus.Info(fmt.Sprintf("GetPerfumeList recommendedList redis SetCtx err: %+v", err))
		}

		err = l.svcCtx.BizRedis.ExpireCtx(l.ctx, cachex.ForumRecommendBrand, l.svcCtx.Config.Query.ExpireTime)
		if err != nil {
			logrus.Info(fmt.Sprintf("GetPerfumeList recommendedList redis SetCtx err: %+v", err))
		}
	} else {
		bids = strings.TrimPrefix(brandIds, todayPrefix)
	}

	var preload string = "blogBook:Id,Title,Slug,Excerpt;blogMedia:Id,Uid,Url,Thumbnail;blogBookDetail:ContentHtml"
	var eagerLoad string = "blogBook:blogMedia,blogBookDetail"
	res, err := l.svcCtx.BlogRpc.BookList(l.ctx, &blogclient.BookListReq{
		Preload:   preload,
		EagerLoad: eagerLoad,
		EntryIds:  bids,
	})
	if err != nil {
		logrus.Info(fmt.Sprintf("GetPerfumeList recommendedList BlogRpc BookList err: %+v", err))
		return
	}

	resp = &types.PerfumeListResp{}
	if len(res.Data) > 0 {
		for _, item := range res.Data {
			var sections []*types.TermInfo
			if len(item.Sections) > 0 {
				for _, v := range item.Sections {
					sections = append(sections, &types.TermInfo{
						Id:   pointy.GetPointer(uint64(v.Id)),
						Name: pointy.GetPointer(v.Name),
						Slug: pointy.GetPointer(v.Slug),
					})
				}
			}
			var media []*types.TmpMediaInfo
			if len(item.Media) > 0 {
				for _, v := range item.Media {
					media = append(media, &types.TmpMediaInfo{
						Uid:       pointy.GetPointer(uint64(v.Uid)),
						Id:        pointy.GetPointer(strconv.FormatInt(int64(v.Id), 10)),
						Url:       pointy.GetPointer(v.Url),
						Thumbnail: pointy.GetPointer(uint32(v.Thumbnail)),
						AltText:   pointy.GetPointer(v.AltText),
						MetaData:  pointy.GetPointer(v.Meta),
					})
				}
			}
			var excerpt string
			if len(item.Excerpt) > 0 {
				excerpt = item.Excerpt
			} else {
				doc, err := modelx.RemoveHtmlTag(item.ContentHtml)
				if err != nil {
					logrus.Info(fmt.Sprintf("recommendedList RemoveHtmlTag err: %+v", err))
				} else {
					excerptRune := []rune(doc)
					if len(excerptRune) > 200 {
						excerpt = string(excerptRune[0:197]) + "..."
					}
				}
			}

			resp.RecBrands = append(resp.RecBrands, &types.KbEntryInfo{
				Id:       pointy.GetPointer(uint64(item.Id)),
				Title:    pointy.GetPointer(item.Title),
				Slug:     pointy.GetPointer(item.Slug),
				Excerpt:  pointy.GetPointer(excerpt),
				Sections: sections,
				Media:    media,
			})
		}

		l.svcCtx.LocalCache.Set(cachex.ForumBrand, resp)

		return resp, true
	}

	return
}
